using System;
using System.Collections.Generic;
using Sirenix.OdinInspector;
using UnityEngine;
using UnityEngine.UI;

namespace Utils.MaterialProperty.UGUI
{
    [System.Serializable][AddComponentMenu("Material Property/UGUI/Setter")]
    public class MaterialPropertyUni : MonoBehaviour
    {
        [SerializeField] Graphic currentGraphic;
        [SerializeField] [OnValueChanged("AsyncData")] protected MaterialPropertyType propertyType;
        
        [SerializeField] [OnValueChanged("AsyncData")] [ShowIf("IsVector")] private VectorType vectorType;
        
        [SerializeField] [OnValueChanged("SetPropertyId")] [ValueDropdown("GetPropertyNames")] [OnValueChanged("ReadDefault")]protected string propertyName;
        [HideInInspector] public string propertyDisplayName;
        [ShowInInspector] [ReadOnly] private int propertyId;
        
        
        [SerializeField] [OnValueChanged("AsyncData")] [ShowIf("IsInteger")][ShowIf("IsFloat")] private float min;
        [SerializeField] [OnValueChanged("AsyncData")] [ShowIf("IsInteger")][ShowIf("IsFloat")] private float max;
        
        [SerializeField] [OnValueChanged("OnPropertyValueChange")] [ShowIf("IsFloat")] [PropertyRange("min","max")] [LabelText("@propertyDisplayName")] private float floatValue;

        [SerializeField] [OnValueChanged("OnPropertyValueChange")] [ShowIf("IsInteger")] [PropertyRange("min","max")] [LabelText("@propertyDisplayName")] private int intValue;
        
        [SerializeField] [OnValueChanged("OnPropertyValueChange")] [LabelText("@propertyDisplayName + \"-x\"")] [ShowIf("IsVector2")]private float x;
        [SerializeField] [OnValueChanged("OnPropertyValueChange")] [LabelText("@propertyDisplayName + \"-y\"")] [ShowIf("IsVector2")]private float y;
        [SerializeField] [OnValueChanged("OnPropertyValueChange")] [LabelText("@propertyDisplayName + \"-z\"")] [ShowIf("IsVector3")] private float z;
        [SerializeField] [OnValueChanged("OnPropertyValueChange")] [LabelText("@propertyDisplayName + \"-w\"")] [ShowIf("IsVector4")] private float w;
        [SerializeField] [OnValueChanged("OnPropertyValueChange")] [ShowIf("IsColor")] [LabelText("@propertyDisplayName")] private Color color = Color.white;
        
        [SerializeField] [OnValueChanged("OnPropertyValueChange")] [ShowIf("IsMatrix")] [LabelText("@propertyDisplayName")] private Matrix4x4 matrix;
        [SerializeField] [OnValueChanged("OnPropertyValueChange")] [ShowIf("IsTexture")] [LabelText("@propertyDisplayName")] private Texture texture;

        [SerializeReference][InfoBox("暂不支持")]//[InfoBox("需要实现你的IConstantBufferProcessor")]
        [ShowIf("IsConstantBuffer")] [OnValueChanged("OnConstantBufferProcessorChanged")]private IConstantBufferProcessor constantBufferProcessor ;
        
        [SerializeReference][InfoBox("暂不支持")]
        [ShowIf("IsComputeBuffer")] [OnValueChanged("OnConstantBufferProcessorChanged")]private IConstantBufferProcessor computeBufferProcessor ;

        private MaterialPropertyUniData _cacheData;
        public void ReadData(MaterialPropertyUniData data)
        {
            _cacheData = data;
            AsyncData();
        }
        
        public void ApplyData(MaterialPropertyUniData data)
        {
            propertyType = data.propertyType;
            vectorType = data.vectorType;
            min = data.min;
            max = data.max;
            floatValue = data.floatValue;
            intValue = data.intValue;
            x = data.x;
            y = data.y;
            z = data.z;
            w = data.w;
            texture = data.texture;
            color = data.color;
            matrix = data.matrix;
        }

        private void AsyncData()
        {
            if (_cacheData == null)
                return;
            _cacheData.propertyDisplayName = propertyDisplayName;
            _cacheData.propertyType = propertyType;
            _cacheData.vectorType = vectorType;
            _cacheData.min = min;
            _cacheData.max = max;
            _cacheData.floatValue = floatValue;
            _cacheData.intValue = intValue;
            _cacheData.x = x;
            _cacheData.y = y;
            _cacheData.z = z;
            _cacheData.w = w;
            _cacheData.texture = texture;
            _cacheData.color = color;
            _cacheData.matrix = matrix;
        }

        private void OnPropertyValueChange()
        {
            AsyncData();
            SetMaterialProperty();
            currentGraphic.SetMaterialDirty();
        }
        
        private IEnumerable<string> GetPropertyNames()
        {
            if (currentGraphic != null)
            {
                var types = currentGraphic.material.GetPropertyNames(propertyType);
                yield return "";
                foreach (var item in types)
                {
                    yield return item;
                }
            }
        }
        
        public int PropertyId => propertyId;
        public MaterialPropertyType PropertyType => propertyType;
        
        public void SetGraphic(Graphic graphic)
        {
            currentGraphic = graphic;
            propertyId = Shader.PropertyToID(propertyName);
        }
        
        public void SetPropertyId()
        {
            propertyId = Shader.PropertyToID(propertyName);
            propertyDisplayName = propertyName.Trim('_');
        }
        
#if UNITY_EDITOR
        
        private int GetPropertyIndex()
        {
            var shader = currentGraphic.material.shader;
            for (int i = 0; i < UnityEditor.ShaderUtil.GetPropertyCount(shader); i++)
            {
                string name = UnityEditor.ShaderUtil.GetPropertyName(shader, i);
                if (name == propertyName)
                    return i;
            }

            return -1;
        }

        protected void ReadDefault()
        {
            if (string.IsNullOrEmpty(propertyName))
                return;
#if UNITY_EDITOR
            var index = GetPropertyIndex();
            var type = UnityEditor.ShaderUtil.GetPropertyType(currentGraphic.material.shader, index);
            bool tempIsRange = type == UnityEditor.ShaderUtil.ShaderPropertyType.Range;
#endif
            switch (this.propertyType)
            {
                case MaterialPropertyType.Float:
#if UNITY_EDITOR
                    if (tempIsRange)
                    {
                        min = UnityEditor.ShaderUtil.GetRangeLimits(currentGraphic.material.shader, index, 1);
                        max = UnityEditor.ShaderUtil.GetRangeLimits(currentGraphic.material.shader, index, 2);
                    }
                    else
                    {
                        min = 0;
                        max = 1;
                    }
#endif
                    floatValue = currentGraphic.material.GetFloat(this.PropertyId);
                    break;
                case MaterialPropertyType.Int:
#if UNITY_EDITOR
                    if (tempIsRange)
                    {
                        min = Mathf.RoundToInt(UnityEditor.ShaderUtil.GetRangeLimits(currentGraphic.material.shader, index, 1));
                        max =  Mathf.RoundToInt(UnityEditor.ShaderUtil.GetRangeLimits(currentGraphic.material.shader, index, 2));
                    }
                    else
                    {
                        min = 0;
                        max = 2;
                    }
#endif
                    intValue = currentGraphic.material.GetInt(this.PropertyId);
                    break;
                case MaterialPropertyType.Vector:
                    if (vectorType == VectorType.Color)
                    {
                        color = currentGraphic.material.GetColor(this.PropertyId);
                    }
                    else if (vectorType != VectorType.RectSize)
                    {
                        Vector4 vector = currentGraphic.material.GetVector(this.PropertyId);
                        x = vector.x;
                        y = vector.y;
                        if (vectorType == VectorType.Vector3)
                        {
                            z = vector.z;
                        }
                        else if (vectorType == VectorType.Vector4)
                        {
                            z = vector.z;
                            w = vector.w;
                        }
                    }
                    else
                    {
                        currentGraphic.material.SetVector(propertyId, new Vector4(currentGraphic.rectTransform.rect.width, currentGraphic.rectTransform.rect.height, 0, 0));
                    }
                    break;
                case MaterialPropertyType.Matrix:
                    matrix = currentGraphic.material.GetMatrix(this.PropertyId);
                    break;
                case MaterialPropertyType.Texture:
                    texture = currentGraphic.material.GetTexture(this.PropertyId);
                    break;
            }
        }

        private void Reset()
        {
            currentGraphic = gameObject.GetComponent<Graphic>();
        }
#endif
        

        public void OnRectTransformChange()
         {
             if (IsVector && vectorType == VectorType.RectSize)
             {
                 currentGraphic.material.SetVector(propertyId, new Vector4(currentGraphic.rectTransform.rect.width, currentGraphic.rectTransform.rect.height, 0, 0));
             }
         }

         public void SetMaterialProperty()
         {
             // if(propertyId<=0)
             //     propertyId = Shader.PropertyToID(propertyName);
             
             if (string.IsNullOrEmpty(propertyName) || propertyId == 0)
                 return;
             
             switch (this.propertyType)
             {
                 case MaterialPropertyType.Float:
                      currentGraphic.material.SetFloat(this.PropertyId,floatValue);
                     break;
                 case MaterialPropertyType.Int:
                      currentGraphic.material.SetInt(this.PropertyId,intValue);
                     break;
                 case MaterialPropertyType.Vector:
                     if (vectorType == VectorType.Color)
                     {
                         currentGraphic.material.SetColor(this.PropertyId,color);
                     }
                     else if (vectorType != VectorType.RectSize)
                     {
                         Vector4 vector = new Vector4(x, y, 0, 0);
                         if (vectorType == VectorType.Vector3)
                         {
                             vector.z =  z;
                         }
                         else if (vectorType == VectorType.Vector4)
                         {
                             vector.z =  z;
                             vector.w =  w;
                         }
                         currentGraphic.material.SetVector(this.PropertyId, vector);
                     }
                     break;
                 case MaterialPropertyType.Matrix:
                     currentGraphic.material.SetMatrix(this.PropertyId, matrix);
                     break;
                 case MaterialPropertyType.Texture:
                     currentGraphic.material.SetTexture(this.PropertyId, texture);
                     break;
                 case MaterialPropertyType.ConstantBuffer:
                     constantBufferProcessor?.SetContent();
                     break;
             }
         }

         void OnConstantBufferProcessorChanged()
         {
             if (constantBufferProcessor != null)
             {
                 constantBufferProcessor.InInitialize(currentGraphic,this.propertyName);
             }
         }
         
         protected bool IsFloat => propertyType == MaterialPropertyType.Float;
         protected bool IsInteger => propertyType == MaterialPropertyType.Int;
         protected bool IsVector => propertyType == MaterialPropertyType.Vector;
         protected bool IsMatrix => propertyType == MaterialPropertyType.Matrix;
         protected bool IsTexture => propertyType == MaterialPropertyType.Texture;
         protected bool IsConstantBuffer => propertyType == MaterialPropertyType.ConstantBuffer;
         protected bool IsComputeBuffer => propertyType == MaterialPropertyType.ComputeBuffer;
         protected bool IsVector2 => ((int)vectorType & (int)VectorType.Vector2) == (int)VectorType.Vector2;
         protected bool IsVector3 => ((int)vectorType & (int)VectorType.Vector3) == (int)VectorType.Vector3;
         protected bool IsVector4 => ((int)vectorType & (int)VectorType.Vector4) == (int)VectorType.Vector4;
         protected bool IsColor => ((int)vectorType & (int)VectorType.Color) == (int)VectorType.Color;
         protected bool IsRectSize => ((int)vectorType & (int)VectorType.RectSize) == (int)VectorType.RectSize;
     }
}
